﻿using System;
using System.IO;
using System.Threading;

namespace sinetd.Protocols.Files
{
    public class Protocol : BaseProtocol, IProtocol
    {
        FileSystemWatcher send_notifier;
        FileSystemWatcher recv_notifier;

        public override void ConfigDefault()
        {
            cfg.SetDefault("port", "tmp/");
            cfg.SetDefault("prefix", "0");

            cfg.SetDefault("sufix_cnt", ".cnt");
            cfg.SetDefault("sufix_ans", "*.ans");
            cfg.SetDefault("sufix_ask", "*.ask");
        }

        public Protocol() : base("Files") { }

        public Protocol(string config, string id) : base("Files", id)
        {
            this.Config = config;
            setup_notifiers(true);
        }

        #region IProtocol Members

        public void Bind(AcceptClientHandler AcceptClient)
        {
            Directory.CreateDirectory(cfg.GetString("port"));
            Directory.CreateDirectory(cfg.GetString("port") + "arch");
            deleteDir = true;

            StartThread(() =>
            {
                FileSystemWatcher fsw = new FileSystemWatcher();
                fsw.Path = cfg.GetString("port");
                fsw.Filter = "*" + cfg.GetString("sufix_cnt");
                fsw.NotifyFilter = NotifyFilters.FileName;
                FileSystemEventHandler createConn = new FileSystemEventHandler((object sender, FileSystemEventArgs e) =>
                {
                    try
                    {
                        string pre = sinetd.Utilities.Misc.FileGetContents(e.FullPath).Trim();
                        Protocol p = new Protocol(Config + ";prefix=" + pre, Id);
                        if (File.Exists(e.FullPath)) File.Delete(e.FullPath);
                        AcceptClient(p);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                });

                fsw.Created += createConn;
                fsw.EnableRaisingEvents = true;

                //maybe can handle some connection request
                string[] files = Directory.GetFiles(cfg.GetString("port"), "*" + cfg.GetString("sufix_cnt"));
                foreach (string file in files)
                {
                    createConn(null, new FileSystemEventArgs(WatcherChangeTypes.Created, cfg.GetString("port"), Path.GetFileName(file)));
                }

                while (!TerminatePending) { Thread.Sleep(100); }

                fsw.EnableRaisingEvents = false;
            });
        }

        public bool Connect()
        {
            FileSystemWatcher fsw = new FileSystemWatcher();
            fsw.NotifyFilter = NotifyFilters.FileName;
            fsw.Filter = "*" + cfg.GetString("sufix_cnt");
            fsw.Deleted += send_notifier_Deleted;

            cfg.SetString("prefix", sinetd.Utilities.Misc.RandomString(8));

            try
            {
                fsw.Path = cfg.GetString("port");    
                fsw.EnableRaisingEvents = true;

                using (StreamWriter sw = File.CreateText(cfg.GetString("port") + cfg.GetString("prefix") + sinetd.Utilities.Misc.RandomString(8) + cfg.GetString("sufix_cnt")))
                {
                    sw.WriteLine(cfg.GetString("prefix"));
                }
            }
            catch (Exception)
            {
                //throw new Exception("Can't connect");
                return false;
            }

            bool timeout = mreSend.WaitOne(3000);
            if (!timeout) throw new Exception("Timeout");
            mreSend.Reset();

            fsw.EnableRaisingEvents = false;
            fsw.Deleted -= send_notifier_Deleted;

            setup_notifiers(false);

            return true;
        }

        public void Send(string msg)
        {
            mreRead.Reset();
            try
            {
                using (StreamWriter sw = File.CreateText(cfg.GetString("port") + send_notifier.Filter.Replace("*", sinetd.Utilities.Misc.RandomString(8))))
                {
                    sw.WriteLine(msg);
                }
                bool timeout = mreSend.WaitOne(SendTimeout);
                if (!timeout) throw new Exception("Timeout");
                
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        public string Recv()
        {
            mreSend.Reset();
            bool timeout = mreRead.WaitOne(ReceiveTimeout);
            if (!timeout) throw new Exception("Timeout");
            
            string ret = sinetd.Utilities.Misc.FileGetContents(fileToRecv);
            if (File.Exists(fileToRecv))
            {
                File.Move(fileToRecv, cfg.GetString("port") + "arch/" + Path.GetFileName(fileToRecv));
                //File.Delete(fileToRecv);
            }
            return ret;
        }

        public void Close()
        {
            mreRead.Set();

            StopThread();

            if (recv_notifier != null)
            {
                recv_notifier.EnableRaisingEvents = false;
                recv_notifier.Dispose();
            }

            if (send_notifier != null)
            {
                send_notifier.EnableRaisingEvents = false;
                send_notifier.Dispose();
            }

            try
            {
                if (deleteDir)
                    Directory.Delete(cfg.GetString("port"), true);
            }
            catch { }
        }

        #endregion

        private void setup_notifiers(bool srv)
        {
            recv_notifier = new FileSystemWatcher();
            send_notifier = new FileSystemWatcher();

            recv_notifier.Path = send_notifier.Path = cfg.GetString("port");
            recv_notifier.NotifyFilter = send_notifier.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;

            recv_notifier.Filter = cfg.GetString("prefix") + (srv ? cfg.GetString("sufix_ask") : cfg.GetString("sufix_ans"));
            send_notifier.Filter = cfg.GetString("prefix") + (srv ? cfg.GetString("sufix_ans") : cfg.GetString("sufix_ask"));

            recv_notifier.Created += new FileSystemEventHandler(recv_notifier_Created);
            send_notifier.Deleted += new FileSystemEventHandler(send_notifier_Deleted);

            recv_notifier.EnableRaisingEvents = send_notifier.EnableRaisingEvents = true;
        }

        private void recv_notifier_Created(object sender, FileSystemEventArgs e)
        {
            fileToRecv = e.FullPath;
            mreRead.Set();
        }

        private void send_notifier_Deleted(object sender, FileSystemEventArgs e)
        {
            //yes he received
            mreSend.Set();
        }

        private bool deleteDir = false;
        private string fileToRecv = "";
        ManualResetEvent mreRead = new ManualResetEvent(false);
        ManualResetEvent mreSend = new ManualResetEvent(false);

        #region Timeouts

        private int receiveTimeout = 0;
        private int sendTimeout = 0;

        public int ReceiveTimeout { get { return receiveTimeout == 0 ? -1 : receiveTimeout; } set { receiveTimeout = value; } }
        public int SendTimeout { get { return sendTimeout == 0 ? -1 : sendTimeout; } set { sendTimeout = value; } }

        #endregion
    }
}
